using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using LibGreen.Jabber.Utility;

namespace LibGreen.Jabber.Connection
{
    /// <summary>
    /// A delegate that handles read requests from an XmlReader
    /// </summary>
    public delegate void ReadCompleteHandler(object sender, ReadCompleteEventArgs e);

    /// <summary>
    /// A delegate that handles ReadSubtree requests from an XmlReader
    /// </summary>
    public delegate void ReadSubtreeCompleteHandler(object sender, ReadSubtreeCompleteEventArgs e);

    /// <summary>
    /// Encapsulates the results of an asynchronous read operation
    /// </summary>
    public class ReadCompleteEventArgs : EventArgs
    {
        private readonly XmlReader state;
        private readonly bool result;

        /// <summary>
        /// Initializes a new ReadCompleteEventArgs
        /// </summary>
        /// <param name="state">The current state of the underlying <see cref="XmlReader"/></param>
        /// <param name="result">The result of the asynchronous read operation</param>
        public ReadCompleteEventArgs(XmlReader state, bool result)
        {
            this.state = state;
            this.result = result;
        }

        /// <summary>
        /// Gets the current state of the underlying <see cref="XmlReader"/>
        /// </summary>
        public XmlReader Reader
        {
            get { return state; }
        }

        /// <summary>
        /// Gets the result of the asynchronous read operation
        /// </summary>
        public bool Result
        {
            get { return result; }
        }
    }

    /// <summary>
    /// Encapsulates the results of an asynchronous ReadSubtree operation
    /// </summary>
    public class ReadSubtreeCompleteEventArgs : EventArgs
    {
        private readonly XmlReader state;
        private readonly XmlReader subtree;

        /// <summary>
        /// Initializes a new ReadSubtreeCompleteEventArgs
        /// </summary>
        /// <param name="state">The current state of the underlying <see cref="XmlReader"/></param>
        /// <param name="subtree">The subtree read by <see cref="AsyncXmlReader.ReadSubtreeAsync"/></param>
        public ReadSubtreeCompleteEventArgs(XmlReader state, XmlReader subtree)
        {
            this.state = state;
            this.subtree = subtree;
        }

        /// <summary>
        /// Gets the current state of the underlying <see cref="XmlReader"/>
        /// </summary>
        public XmlReader Reader
        {
            get { return state; }
        }

        /// <summary>
        /// Gets the subtree read by <see cref="AsyncXmlReader.ReadSubtreeAsync"/>
        /// </summary>
        public XmlReader Subtree
        {
            get { return subtree; }
        }

    }

    /// <summary>
    /// Implements an asynchronous wrapper over a basic XmlReader
    /// </summary>
    public class AsyncXmlReader
    {
        private readonly Stream baseStream;
        private readonly XmlReaderSettings settings;
        private XmlReader reader;
        private bool isBusy;
        private bool isClosing;

        /// <summary>
        /// Initializes a new AsyncXmlReader
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> containing the XML data</param>
        public AsyncXmlReader(Stream stream)
            : this(stream, null)
        {
        }

        /// <summary>
        /// Initializes a new AsyncXmlReader
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> containing the XML data</param>
        /// <param name="settings">The <see cref="XmlReaderSettings"/> used to configure this object</param>
        public AsyncXmlReader(Stream stream, XmlReaderSettings settings)
        {
            baseStream = stream;
            this.settings = settings;
        }

        /// <summary>
        /// Closes the underlying <see cref="XmlReader"/>
        /// </summary>
        /// <seealso cref="XmlReader.Close"/>
        public void Close()
        {
            isClosing = true;
            if (reader != null)
            {
                reader.Close();
            }
            isBusy = false;
        }

        /// <summary>
        /// Raised when the result of a <see cref="ReadAsync"/> operation is avaliable
        /// </summary>
        public event ReadCompleteHandler ReadComplete;

        /// <summary>
        /// Raised when the result of a <see cref="ReadToFollowingAsync"/> operation is available
        /// </summary>
        public event ReadCompleteHandler ReadToFollowingComplete;

        /// <summary>
        /// Raised when the result of a <see cref="ReadSubtreeAsync"/> operation is available
        /// </summary>
        public event ReadSubtreeCompleteHandler ReadSubtreeComplete;

        /// <summary>
        /// Reads the next node from the stream
        /// </summary>
        /// <returns>A value indicating whether the read operation was successful</returns>
        /// <exception cref="InvalidOperationException">Thrown when an asynchronous read is in progress</exception>
        public bool Read()
        {
            CheckAsync();
            return ReadCore();
        }

        /// <summary>
        /// Asynchronously reads the next node from the stream
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown when an asynchronous read is in progress</exception>
        public void ReadAsync()
        {
            CheckAsync();
            ReadHandler handler = ReadCore;
            handler.BeginInvoke(EndRead, handler);
        }

        /// <summary>
        /// Reads until an element with the specified name is found
        /// </summary>
        /// <param name="name">The qualified name of the element</param>
        /// <returns>A value indicating whether the read operation was successful</returns>
        /// <exception cref="InvalidOperationException">Thrown when an asynchronous read is in progress</exception>
        public bool ReadToFollowing(string name)
        {
            CheckAsync();
            return ReadToFollowingCore(name);
        }

        /// <summary>
        /// Reads until an element with the specified name is found
        /// </summary>
        /// <param name="name">The qualified name of the element</param>
        /// <returns>A value indicating whether the read operation was successful</returns>
        /// <exception cref="InvalidOperationException">Thrown when an asynchronous read is in progress</exception>
        public void ReadToFollowingAsync(string name)
        {
            CheckAsync();
            ReadToFollowingHandler handler = ReadToFollowingCore;
            handler.BeginInvoke(name, EndReadToFollowing, handler);
        }

        /// <summary>
        /// Returns a new XmlReader instance that can be used to read the current node, and all its descendants
        /// </summary>
        public XmlReader ReadSubtree()
        {
            CheckAsync();
            return ReadSubtreeCore();
        }

        /// <summary>
        /// Asynchronously creates a new XmlReader instance that can be used to read the current node, and all its descendants
        /// </summary>
        public void ReadSubtreeAsync()
        {
            CheckAsync();
            ReadSubtreeHandler handler = ReadSubtreeCore;
            handler.BeginInvoke(EndReadSubtree, handler);
        }

        #region Asynchronous support methods
        /// <summary>
        /// Provides a handler to delegate read methods internally
        /// </summary>
        /// <returns>The result of the read operation</returns>
        private delegate bool ReadHandler();

        /// <summary>
        /// Provides a handler to delegate ReadToFollowing methods internally
        /// </summary>
        /// <param name="name">The qualified name of the element</param>
        /// <returns>The result of the read operation</returns>
        private delegate bool ReadToFollowingHandler(string name);

        /// <summary>
        /// Provides a handler to delegate ReadSubtree methods internally
        /// </summary>
        /// <returns>The result of the read operation</returns>
        private delegate XmlReader ReadSubtreeHandler();

        /// <summary>
        /// Reads the next node from the stream, ensuring that the reader is created
        /// </summary>
        /// <returns>A value indicating whether the read operation was successful</returns>
        private bool ReadCore()
        {
            EnsureReader();
            bool retval = false;
            try
            {
                retval = reader.Read();
                if (retval && reader.NodeType == XmlNodeType.XmlDeclaration)
                {
                    reader.Skip();
                }
            }
            catch (XmlException exception)
            {
                Logging.LogString("Caught XmlException in AsyncXmlReader.ReadCore: {0}", exception);
            }
            catch (IOException ioex)
            {
                if (!isClosing)
                {
                    Logging.LogString("Caught IOException in AsyncXmlReader.ReadCore: {0}", ioex);
                    throw;
                }
            }

            isBusy = false;
            return retval;
        }

        /// <summary>
        /// Reads until an element with the specified name is found, ensuring that the reader is created
        /// </summary>
        /// <param name="name">The qualified name of the element</param>
        /// <returns>A value indicating whether the read operation was successful</returns>
        private bool ReadToFollowingCore(string name)
        {
            EnsureReader();
            bool retval = false;
            try
            {
                retval = reader.ReadToFollowing(name);
                if (retval && reader.NodeType == XmlNodeType.XmlDeclaration)
                {
                    reader.Skip();
                }
            }
            catch (XmlException exception)
            {
                Logging.LogString("Caught XmlException in AsyncXmlReader.ReadToFollowingCore: {0}", exception);
            }
            catch (IOException ioex)
            {
                if (!isClosing)
                {
                    Logging.LogString("Caught IOException in AsyncXmlReader.ReadCore: {0}", ioex);
                    throw;
                }
            }

            isBusy = false;
            return retval;
        }

        /// <summary>
        /// Returns a new XmlReader instance that can be used to read the current node, and all its descendants, ensuring that the reader is created
        /// </summary>
        private XmlReader ReadSubtreeCore()
        {
            EnsureReader();
            XmlReader retval = null;
            try
            {
                retval = reader.ReadSubtree();
            }
            catch (InvalidOperationException exception)
            {
                Logging.LogString("Caught InvalidOperationException in AsyncXmlReader.ReadSubtreeCore: {0}", exception);
            }
            catch (IOException ioex)
            {
                if (!isClosing)
                {
                    Logging.LogString("Caught IOException in AsyncXmlReader.ReadCore: {0}", ioex);
                    throw;
                }
            }

            isBusy = false;
            return retval;
        }

        /// <summary>
        /// An <see cref="AsyncCallback"/> implementation that completes a ReadAsync operation
        /// </summary>
        /// <param name="res">The <see cref="IAsyncResult"/> of the asynchronous method invocation</param>
        private void EndRead(IAsyncResult res)
        {
            bool result = false;
            ReadHandler state = res.AsyncState as ReadHandler;
            if (state != null)
            {
                result = state.EndInvoke(res);
            }

            if (ReadComplete != null)
            {
                ReadComplete(this, new ReadCompleteEventArgs(reader, result));
            }
        }

        /// <summary>
        /// An <see cref="AsyncCallback"/> implementation that completes a ReadToFollowingAsync operation
        /// </summary>
        /// <param name="res">The <see cref="IAsyncResult"/> of the asynchronous method invocation</param>
        private void EndReadToFollowing(IAsyncResult res)
        {
            bool result = false;
            ReadToFollowingHandler state = res.AsyncState as ReadToFollowingHandler;
            if (state != null)
            {
                result = state.EndInvoke(res);
            }

            if (ReadToFollowingComplete != null)
            {
                ReadToFollowingComplete(this, new ReadCompleteEventArgs(reader, result));
            }
        }

        /// <summary>
        /// An <see cref="AsyncCallback"/> implementation that completes a ReadSubtreeAsync operation
        /// </summary>
        /// <param name="res">The <see cref="IAsyncResult"/> of the asynchronous method invocation</param>
        private void EndReadSubtree(IAsyncResult res)
        {
            XmlReader result = null;
            ReadSubtreeHandler state = res.AsyncState as ReadSubtreeHandler;
            if (state != null)
            {
                result = state.EndInvoke(res);
            }

            if (ReadSubtreeComplete != null)
            {
                ReadSubtreeComplete(this, new ReadSubtreeCompleteEventArgs(reader, result));
            }
        }
        #endregion

        /// <summary>
        /// Gets a value indicating whether an asynchronous method is in progress
        /// </summary>
        public bool IsBusy
        {
            get { return isBusy; }
        }

        /// <summary>
        /// Gets a reference to the underlying <see cref="XmlReader"/>
        /// </summary>
        public XmlReader Base
        {
            get { return reader; }
        }

        /// <summary>
        /// Checks that a read operation is clear to proceed
        /// </summary>
        private void CheckAsync()
        {
            lock (this)
            {
                if (isBusy)
                {
                    throw new InvalidOperationException("A previous call to a Read*Async method has not completed");
                }
                isBusy = true;
            }
        }

        /// <summary>
        /// Ensures that the XmlReader is created
        /// </summary>
        /// <remarks>Reader creation is deferred until a read is requested.  If the underlying stream has no data to be read,
        /// the creation will block until data arrives.  That is not very asynchronous.</remarks>
        private void EnsureReader()
        {
            lock (this)
            {
                if (reader == null)
                {
                    lock (this)
                    {
                        reader = XmlReader.Create(baseStream);
                    }
                }
            }
        }
    }
}
